# OTableau : WYSIWYG LaTeX code generator for OT tableaux 
# Copyright (C) 2006 Julien Eychenne
# 
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 
# MA  02110-1301, USA.

import xml.dom.minidom as xml
from zipfile import *
from time import localtime
import string
import csv
from os.path import splitext, normcase
import OTGrammar
from util.cl import positionif
from util.fnc import car
def addExtension(file, extension):
    if splitext(normcase(file))[1] != '.' + normcase(extension):
        return '%s.%s' % (file, extension)
    else:
        return file

def readFile(file):
    return open(file, 'r').read()
def writeFile(file, text):
    try:
        f = open(file, 'w')
        f.write(text)
        f.close()
        return True
    except:
        return False
# contributed by Nathan Sanders
def readCSV(file):
    cr = csv.reader(open(file, "rb"), delimiter='\t')
    tableau = [cr.next()]
    for line in cr:
        tableau.append([line[0]] + map(int, line[1:]))
    return OTGrammar.Tableau(tableau)
# contributed by Nathan Sanders
def writeCSV(file, gmr):
    try:
        csv.writer(open(file, "wb"),
                   delimiter='\t').writerows(gmr.tableau)
        return True
    except:
        return False
def readOTSoftCSV(file):
    """At first, we will only read the first tableau. Maybe later we can
    create new windows. The problem is that although the constraints are
    the same for all data, there are multiple winners specified, which
    is not possible in current versions of OTableau
    
    So far the algorithm should work, but only one tableau and only one winner
    is supported."""
    rows = list(csv.reader(open(file, "rb"), delimiter='\t'))
    # problem! there can be blanks! so I have to remember how big is the
    # hierarchy + 3 ok.
    con = rows[0][3:]
    try: # check for trailing blanks
        con = con[:con.index('')] # str.index will throw ValueError if there
    except ValueError: # are no trailing blanks
        pass
    # rows[1] is a list of abbreviations for the constraints. I will pass over
    # these for now. (though it is a useful idea)
    hierarchy = con
    input = rows[2][0]
    # you can repeat this if you want to create multiple tableaux from one file
    table_end = positionif(car, rows[3:])
    if table_end is not None:
        table_end += 3 # add back in the skipped constraint rows
    # else: take to end of rows if this is the last table
    rows = [r[:3+len(con)] for r in rows[2:table_end]]
    # we can't directly represent the assertion that X wins (yet);
    # the comparative tableau just says 'only the first is guaranteed to win'
    # but we can at least ensure ONE winner comes in first place.
    rows.sort(lambda l1,l2:-cmp(l1[2], l2[2])) # slow, but 2.3 compatible
    for row in rows: # delete in reverse order so the indices are preserved
        del row[2] # wins?
        del row[0] # input
        for i in range(1, len(row)): # start at 1: skip output
            if row[i]=='':
                row[i] = 0
            else:
                row[i] = int(row[i])
    return OTGrammar.Tableau([[input] + con] + rows)
def writeOTSoftCSV(file, gmr):
    try:
        cands = [['',out,int(i+1 in gmr.winners)] for i,out in enumerate(gmr.outputs)]
        cands[0][0] = gmr.input
        t = [['','',''] + gmr.tableau[0][1:]] * 2 + \
            [prefix + row for prefix, row in zip(cands, gmr.violations)]
        csv.writer(open(file, "wb"), delimiter='\t').writerows(t)
        return True
    except IOError:
        return False
class LaTeXExportError(Exception): pass
def toLaTeX(gmr, **opt):
    # opt and guiTableau are accessed by all the inner functions,
    # but only opt's contents are altered
    guiTableau = gmr.getLaTeXTableau()
    # main is called at the very end of this containing function
    def main():
        if len(guiTableau) > 27:
            raise LaTeXExportError('I cannot export more than 26 candidates')
        #set footnote size
        ftn = ''
        if opt['ftnSize']: ftn = '\\footnotesize '
        
        try: len(gmr.winners)
        except: gmr.winners = []

        if opt["evaled"]:
            row = 'r'
            nbr = 3
        else:
            row = ''
            nbr = 2

        latex = []
        # Header
        latex.append('\\begin{tabular}{|r%sl||' % row)
        latex.append('c|' * len(gmr.hierarchy))
        latex.append('}\\hline\n')
        # Constraints
        try: input = gmr.input
        except AttributeError: input = ''
        if input!='':
            if opt['useTipa']:
                input = '\\%s{%s}' % (opt["ipa"], input)
        latex.append('\\multicolumn{%d}{|c||}{/%s/} ' % (nbr, input))

        for con in gmr.hierarchy:
            txt = '& {\\sc %s} ' % con
            if opt["doRotate"]:
                txt = ('\\rotatebox{%s}' % opt["angle"]) + txt
            latex.append(txt)
        latex.append('\\\\ \\hline\\hline\n')
        # Candidates
        latex.extend(latexRows())
        
        latex.append('\\end{tabular}\n')
        
        # Table environment
        if opt["caption"] == '' and opt["label"] in ('','tab:'):
            return ''.join(latex)
        else:
            # provide default label (always useful)
            if opt["label"] in ('fig:', 'tab:'):
                opt["label"] += opt["caption"].replace(' ',":") \
                                     .replace('(','').replace(')','')
        s = '\\begin{%s}[h]\n\\centering\n' % opt["environment"]
        return s + ''.join(latex) + \
               """
\\caption{%(caption)s}
\\label{%(label)s}
\\end{%(environment)s}

""" % opt
    def latexRows():
        if not opt["evaled"]:
            opt["userShade"] = False
        return map(getLine, range(1, len(guiTableau)))
    def getLine(row):
        guiRow = guiTableau[row]
        ln = []
        ln.append(' %s. & %s %s & ' % (string.lowercase[row-1],
                                       hand(row),
                                       formatCandidate(guiRow[0])) )
        ln.append(' & '.join(guiRow[1:]))
        ln.append('\\\\ \\hline\n')
        return shadeLine(row, guiRow, ln)
    def hand(row):
        if opt["evaled"]:
            if row in gmr.winners:
                return '\\%s &' % opt["hand"]
            else:
                return ' &'
        else:
            return ''
    def formatCandidate(candidate):
        if opt["useTipa"]:
            return '\\%s{%s}' % (opt["ipa"], candidate)
        else:
            return candidate
    def shadeLine(row, guiRow, ln):
        # Shading is made by scanning the row looking for fatal
        # violation. If none is found, the dummy row is not written.
        if not opt["userShade"]: return ''.join(ln)

        dummy = ['\\LCC\n& & ']
        dummy.append(' & ' * (gmr.fatalViolations[row-1]+1))
        dummy.append('&\\%s ' % opt["color"] * \
                     (len(guiRow)-1-(gmr.fatalViolations[row-1]+1)))
        dummy.append('\\\\ \\hline\n')

        return ''.join(dummy) + ''.join(ln) + '\\ECC\n'
    return main()
#   def toPraat():
#       text = []
#       text.append('File type = "ooTextFile"\nObject class = "OTGrammar 1"\n\n')

class ZipFileError(Exception): pass
class OTableauFile:
    def __init__(self, filename):
        self.filename = filename
        self.OTGrammarFile = 'OTGrammar.xml'
        
    def check(self):
        if not is_zipfile(self.filename):
            raise ZipFileError('%s is not a zip file' % self.filename)
        
    def read(self, file="OTGrammar.xml"):
        self.check()
        zip = ZipFile(self.filename, 'r')
        content = zip.read(file)
        zip.close()
        return content
        
    def write(self, data):
        info = ZipInfo(self.OTGrammarFile, localtime()[:6])
        zip = ZipFile(self.filename, 'w')
        zip.writestr(info, data)
        zip.close()
        
    def append(self, data):
        self.check()
        info = ZipInfo(self.OTGrammarFile, localtime()[:6])
        zip = ZipFile(self.filename, 'a')
        zip.writestr(info, data)
        zip.close()
        
def writeOTGrammar(filename, gmr):
    # input
    xml = ['<?xml version="1.0" encoding="UTF-8"?><OTGrammar>']
    xml.append('<header><format version="1.0" /></header>')
    xml.append('<input name="%s" />' % gmr.input)
    # constraints
    xml.append('<hierarchy>')
    xml += ['<constraint name="%s" />' % con for con in gmr.hierarchy]
    xml.append('</hierarchy>')
    # violations
    for cand,viols in zip(gmr.outputs,gmr.violations):
        xml.append('<candidate name="%s">' % cand)
        for entry in zip(gmr.hierarchy, viols):
            xml.append('<violation constraint="%s" number="%d" />' % entry)
        xml.append('</candidate>')
    xml.append('</OTGrammar>')
    # write output
    OTableauFile(filename).write(''.join(xml))

def readOTableauFile(file):
    # As of now, it assumes a sequential storage of violations regarding
    # constraints, so that constraint name is redundant. However this is
    # quite likely to change in the future.
    array = []
    doc = pydom(xml.parseString(OTableauFile(file).read()))
    # Set up input and constraints on first row
    #   get input
    input = doc['input'][0].name
    constraints = doc['hierarchy'][0]['constraint']
    array.append([input]+[con.name for con in constraints])
    # Append each candidate and its violations to array
    for cand in doc['candidate']:
        array.append([cand.name] + \
                     [int(viol.number) for viol in cand['violation']])
    return OTGrammar.Tableau(array)
        

class pydom:
    """Syntax sugar for xml.dom.minidom:
    __getitem__ calls getElementsByTagName and
    __getattr__ calls getAttribute

    This is quite unnecessary but makes readFromOTableauFile much shorter"""
    def __init__(self, mini):
        self.mini = mini
    def __getitem__(self, item):
        return map(pydom, self.mini.getElementsByTagName(item))
    def __getattr__(self, attr):
        if attr in dir(self.mini):
            return getattr(self.mini, attr)
        else:
            return self.mini.getAttribute(attr)
if __name__ == '__main__':
    m = [['tat','Max','NoCoda'], ['tat', '','*'], ['ta','*','']]
    
    f = OTableauFile('toto.otableau')
    print f.getOTGrammar()
#   f.writeOTGrammar(m)
